package View;

import Componenten.LocalTypes;
import Model.Bord;
import Model.Element;
import Model.Level;
import Model.Vak;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Set;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 * Representeert het speelbord (grafische gedeelte)
 * @author Bram Slob en Roderick van Seijen
 */
public class BordView extends JPanel implements LocalTypes, KeyListener, ActionListener{
    private static final int VAKGROOTTE = 36; //aantal pixels per vak
    private final int STAPGROOTTE = 4; //aantal pixels per beweging
    private final int WACHTTIJD = 30; //aantal milliseconden per update van de timer
    private ObjectView objectView;
    private Sokoban sokoban;
    private Timer timer;
    private int miniStappen;
    private Set<Vak> bewogenVakken;
    private Richting laatsteRichting;
    private Richting volgendeRichting;
    private boolean isBewegend;
    private Bord bord;
    
    /**
     * Constructor voor BordView
     * @param sokoban  Sokoban object (hoofdframe en main())
     */
    public BordView(Sokoban sokoban)
    {
        bord = new Bord();
        objectView = new ObjectView();
        timer = new Timer(WACHTTIJD, this);
        laatsteRichting = Richting.ZUID;
        isBewegend = false;
        setFocusable(true);
        setPreferredSize(new Dimension(Level.getAantalKolommen() * VAKGROOTTE, Level.getAantalRijen() * VAKGROOTTE));
        addKeyListener(this);
        this.sokoban = sokoban;
    }
    
    /**
     * @return  Bord-object
     */
    public Bord getBord(){
        return bord;
    }
    
    
    /**
     * Wordt aangeroepen op het moment dat er een letter / cijfer / leesteken wordt ingedrukt.
     * @param e  Keyboard event
     */
    @Override
    public void keyTyped ( KeyEvent e ){  
        
    }   
    
    /**
     * Wordt aangeroepen op het moment dat er een toets wordt ingedrukt.
     * Zet de beweging van de speler in de aangegeven richting in werking.
     * @param e  Keyboard event
     */
    @Override
    public void keyPressed( KeyEvent e){
        beweegSpeler(getRichting(e));
    }  
    
    /**
     * Wordt aangeroepen op het moment dat een toets wordt losgelaten.
     * Stopt de beweging van de speler als de laatst ingedrukte toets is losgelaten.
     * @param e  Keyboard event
     */
    @Override
    public void keyReleased ( KeyEvent e ){
        Richting r = getRichting(e);
        if (r == volgendeRichting){
            isBewegend = false;
        }
    }
    
    /**
     * Geeft de richting terug die bij een gegeven Keyboard event hoort. 
     * Geeft null als er niet een geldige pijltjestoets is gekoppeld aan het event.
     * @param e  Keyboard event
     * @return   Richting die bij de pijltjestoets hoort.
     */
    private Richting getRichting(KeyEvent e){
        int code = e.getKeyCode();
        Richting r = null;
        switch (code) {
            case KeyEvent.VK_KP_LEFT:
            case KeyEvent.VK_LEFT:
                r = Richting.WEST;
                break;
            case KeyEvent.VK_KP_DOWN:
            case KeyEvent.VK_DOWN:
                r = Richting.ZUID;
                break;
            case KeyEvent.VK_KP_RIGHT:
            case KeyEvent.VK_RIGHT:
                r = Richting.OOST;
                break;
            case KeyEvent.VK_KP_UP:
            case KeyEvent.VK_UP:
                r = Richting.NOORD;
                break;
        }
        
        return r;
    }
    
    /**
     * Zet, indien mogelijk, een beweging van de speler naar het buurvak in de aangegeven richting in werking.
     * @param r  Richting waarin het buurvak zich bevindt.
     */
    private void beweegSpeler(Richting r){
        //System.out.println(r);
        if (!bord.isLevelGehaald()){
            isBewegend = true;
            volgendeRichting = r;
            if (!timer.isRunning()){
                laatsteRichting = r;
                bewogenVakken = bord.beweegSpeler(laatsteRichting);
                if (!bewogenVakken.isEmpty()){
                    sokoban.updateStappen();
                    sokoban.updateBezetteEindvakken();
                    miniStappen = 0;
                    isBewegend = true;
                    //System.out.println("Timer start.");
                    timer.start();
                } else {
                    if (volgendeRichting!=laatsteRichting){
                        laatsteRichting = volgendeRichting;
                        beweegSpeler(laatsteRichting);
                    } else {
                        repaint();
                    }
                }
            }
        }
    }
    
    /**
     * Teken de grafische elementen van het speelbord op het scherm.
     * @param g   Graphics object
     */
    private void bouwWereld(Graphics g) {
       
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        g.setColor(new Color(250, 240, 170));
        
        drawVakken(g);
        drawElementen(g);
    }
    
    /**
     * Teken de grafische representaties van de speelvakken op het scherm.
     * @param g   Graphics object
     */
    private void drawVakken(Graphics g){
        Vak[][] vakMatrix = bord.getVakMatrix();
        
        for(int i = 0; i < Level.getAantalRijen() ; i++){
            for(int j = 0; j < Level.getAantalKolommen(); j++){
                
                Vak vak = vakMatrix[i][j];
                if(vak.getVakType() != null){
                    VakType vakType = vak.getVakType();
                    g.drawImage(objectView.getVakImage(vakType), ( j * VAKGROOTTE), ( i * VAKGROOTTE), this);
                }
                
            }
        }
    }
    
    /**
     * Teken de grafische representaties van de elementen die op de vakken staan op het scherm.
     * @param g   Graphics object
     */
    private void drawElementen(Graphics g){
        Vak[][] vakMatrix = bord.getVakMatrix();
        
        for(int i = 0; i < Level.getAantalRijen() ; i++){
            for(int j = 0; j < Level.getAantalKolommen(); j++){
                
                Vak vak = vakMatrix[i][j];
                VakType vakType = vak.getVakType();
                Element el = vak.getElement();
                
                if (el != null) {
                    ElementType elType = el.getElementType();
                    int x = (j * VAKGROOTTE);
                    int y = (i * VAKGROOTTE);
                    Image img = objectView.getElementImage(elType, vakType, laatsteRichting, miniStappen);
                    
                    if (bewogenVakken != null && bewogenVakken.contains(vak)){
                        switch (laatsteRichting) {
                            case NOORD:
                                y = (i + 1) * VAKGROOTTE - (miniStappen * STAPGROOTTE);
                                break;
                            case WEST:
                                x = (j + 1 ) * VAKGROOTTE - (miniStappen * STAPGROOTTE);
                                break;
                            case ZUID:
                                y = (i - 1) * VAKGROOTTE + (miniStappen * STAPGROOTTE);
                                break;
                            case OOST:
                                x = (j -1 ) * VAKGROOTTE + (miniStappen * STAPGROOTTE);
                                break;
                        }
                    }
                    
                    g.drawImage(img, x, y, this);
                }
            }        
        }
    }
    
    /**
     * Teken het speelbord op het scherm.
     * @param g   Graphics object
     */
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        if(bord.getLevelCode() > 0 && !(bord.isSpelUitgespeeld() && !timer.isRunning())){
            bouwWereld(g);
        }
        
    }

    /**
     * @return Grootte van de speelvakken in pixels
     */
    public static int getVakGrootte(){
        return VAKGROOTTE;
    }
    
    /**
     * Doe een grafische update van het scherm
     */
    public void updateScherm(){
        repaint();
    }

    /**
     * Wordt met vaste intervallen uitgevoerd als de timer gestart is. 
     * @param e   ActionEvent gegenereerd door een timer
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        miniStappen = ++miniStappen % (VAKGROOTTE / STAPGROOTTE);
        if (miniStappen == 0) {
            timer.stop();
            //System.out.println("Timer stop.");
            bewogenVakken.clear();
            if (isBewegend){
                beweegSpeler(volgendeRichting);
            }
            repaint();
            checkLevelGehaald();
        } else {
            repaint();
        }
    }    
    
    /**
     * Check of de huidige level uitgespeeld is of niet en voer een relevante actie uit indien waar.
     */
    private void checkLevelGehaald(){
        if (bord.isLevelGehaald()){
            sokoban.updateHighscore();
            if (bord.isSpelUitgespeeld()){
                sokoban.showCredits();
            } else {
                sokoban.showVolgendeLevelKnop();
            }
        }
    }
    
}
